﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Diagnostics;

using MonoTouch.AudioToolbox;
using com.ReinforceLab.AudioModem;
namespace ArduinoiPhoneShield
{
    /// <summary>
    /// 16-bit monoral channel audio interface class that uses Audio queue service.    
    /// Audio queue service can not play and record simultaniously. So this IAudioPHY support only write.
    /// 
    /// Details of the audio queue service are described in the following documents:
    /// Audio queue service programing guide http://developer.apple.com/iphone/library/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html
    /// Audio programing guide http://developer.apple.com/iphone/library/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html
    /// Audio queue service reference http://developer.apple.com/iphone/library/documentation/MusicAudio/Reference/AudioQueueReference/Reference/reference.html
    /// </summary>
    class AudioQueueAudioPHY : IAudioPHY
    {
        #region Variables
        const int _queue_length = 3;
        const int _minimum_buffer_length_ms = 12; // queue buffer length in a unit of millisec.

        readonly int _samplingRate;
        readonly int _bufferLength;
        readonly int _minimumBufferLength;        

        OutputAudioQueue _outputQueue;
        IAudioSource _source;                
        #endregion

        #region Constructor
        public AudioQueueAudioPHY(int samplingRate)
        {
            if (samplingRate <= 0 || samplingRate > 44100)
                throw new ArgumentOutOfRangeException("samplingRate");

            _samplingRate = samplingRate;
            _minimumBufferLength = (int)((_samplingRate * _minimum_buffer_length_ms) / 1000);
            _bufferLength = _minimumBufferLength * 2;
        }
        #endregion

        #region private methods
        void _audioQueue_OutputCompleted(object sender, OutputCompletedEventArgs e)
        {
            fillAudioQueueBuffer(e.IntPtrBuffer);
        }
        void fillAudioQueueBuffer(IntPtr bufPtr)
        {
            int validDataLength;
            var data = _source.OutSignal(out validDataLength);
            int numBytes = validDataLength * 2;
            unsafe
            {
                fixed (short* ptr = data)
                {
                    OutputAudioQueue.FillAudioData(bufPtr, 0, new IntPtr((void*)ptr), 0, numBytes);
                }
            }
            //var quebuf = (AudioQueueBuffer)Marshal.PtrToStructure(bufPtr, typeof(AudioQueueBuffer));
            //quebuf.AudioDataByteSize = 
            _outputQueue.EnqueueBuffer(bufPtr, numBytes, null);
        }
        #endregion

        #region IAudioPHY メンバ
        public bool IsRunning
        {
            get
            {
                if (null == _outputQueue)
                    return false;
                return _outputQueue.IsRunning;
            }
                
        }
        public int SamplingRate {get{return _samplingRate;}}
        public int BufferLength {get{return _bufferLength;}}
        public int MinimumBufferFillLength {get{return _minimumBufferLength;}}
        public bool CanWrite {get{return true;}}
        public bool CanRead { get { return false; } }
        public void Start(IAudioSource source)
        {
            if (IsRunning)
                return;

            if (source == null)
                throw new ArgumentNullException("source");
            _source = source;

            AudioStreamBasicDescription audioFormat = new AudioStreamBasicDescription()
            {
                SampleRate = _samplingRate,
                Format = AudioFormatType.LinearPCM,
                FormatFlags = AudioFormatFlags.LinearPCMIsSignedInteger | AudioFormatFlags.IsPacked,
                FramesPerPacket = 1,
                ChannelsPerFrame = 1,  // monoral
                BitsPerChannel = 16, // 16-bit
                BytesPerPacket = 2,
                BytesPerFrame = 2,
                Reserved = 0
            };
            
            // output queue
            int bufferByteSize = _bufferLength * audioFormat.BytesPerPacket;            
            IntPtr bufferPtr;
            _outputQueue = new OutputAudioQueue(audioFormat);
            _outputQueue.OutputCompleted += new EventHandler<OutputCompletedEventArgs>(_audioQueue_OutputCompleted);
            for (int index = 0; index < _queue_length; index++)
            {
                _outputQueue.AllocateBuffer(bufferByteSize, out bufferPtr);
                fillAudioQueueBuffer(bufferPtr);
            }            

            // start queue
            _outputQueue.Start();
        }
        public void Stop(bool shouldStopImmediate)
        {
            if (!IsRunning)
                return;

            if (null != _outputQueue)
            {
                _outputQueue.Stop(shouldStopImmediate);
                _outputQueue.Dispose();
                _outputQueue = null;
                _source = null;
            }
        }

        #endregion

        #region IDisposable メンバ
        public void Dispose()
        {
            Stop(true);
        }
        #endregion        
    }
}
